/**
 *
 */
package com.onstar.cn.map;

import android.content.Context;
import android.os.Message;

import com.amap.api.maps2d.AMap;
import com.amap.api.services.core.AMapException;
import com.amap.api.services.core.LatLonPoint;
import com.amap.api.services.core.PoiItem;
import com.amap.api.services.geocoder.GeocodeResult;
import com.amap.api.services.geocoder.GeocodeSearch;
import com.amap.api.services.geocoder.GeocodeSearch.OnGeocodeSearchListener;
import com.amap.api.services.geocoder.RegeocodeAddress;
import com.amap.api.services.geocoder.RegeocodeQuery;
import com.amap.api.services.geocoder.RegeocodeResult;
import com.amap.api.services.geocoder.RegeocodeRoad;
import com.amap.api.services.geocoder.StreetNumber;
import com.amap.api.services.poisearch.PoiResult;
import com.amap.api.services.poisearch.PoiSearch;
import com.amap.api.services.road.Crossroad;
import com.onstar.cn.map.common.Constants;
import com.onstar.cn.map.common.ExceptionType;
import com.onstar.cn.map.common.MapException;
import com.onstar.cn.map.common.PoiItemInfo;
import com.onstar.cn.map.common.PoiType;
import com.onstar.cn.map.common.QueryType;
import com.onstar.cn.map.common.SearchResults;
import com.onstar.cn.map.core.Circle;
import com.onstar.cn.map.listener.OSearchHandlerListener;
import com.onstar.cn.map.util.MessageHandler;
import com.onstar.cn.map.util.QueryOptions;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * This class is to do searching by different searching type: by key name,
 * circle, rectangle and point.
 *
 * @author Qiying Gong
 * @Created 2013-4-24
 */
public class MapSearch {

    private OMapActivity mapActivity;
    private Context context;
    private AMap mapView;
    private MessageHandler msgHandler;

    private PoiResult mPoiPagedResult = null;
    private int currentPage = 0;
    private int totalPage = -1;
    private QueryType queryType;
    private PoiSearch.Query queryOptions;
    private PoiSearch poiSearch;
    private List<PoiItemInfo> mSearchResultList = new ArrayList<PoiItemInfo>();

    public MapSearch(OMapActivity mapActivity, AMap mapView) {
        this.mapActivity = mapActivity;
        this.mapView = mapView;
        msgHandler = new MessageHandler(this.mapView);
    }

    public MapSearch(Context context, AMap mapView, MessageHandler msgHandler) {
        this.context = context;
        this.msgHandler = msgHandler;
    }

    public long searchByName(String key, PoiType poiType, String cityCode,
                             QueryOptions options) {
        clear();
        long id = System.currentTimeMillis();
        checkQueryOptions(id, options);
        queryOptions = null;
        queryOptions = new PoiSearch.Query(key, poiType.getStringValue(),
                cityCode);
        queryOptions.setPageNum(options.getiPageNumber());
        queryOptions.setPageSize(options.getiPageCount());
        this.queryType = QueryType.KEY_WORD;
        poiSearch = null;
        poiSearch = new PoiSearch(mapActivity != null ? mapActivity.getApplicationContext() : context, queryOptions);

//		ExecutorService exec = Executors.newSingleThreadExecutor();
//		exec.execute(new SearchThread(id, poiSearch, options));
//		exec.shutdown();
        new Thread(new SearchThread(id, poiSearch, options)).start();
        return id;
    }

    public long searchNearBy(String key, PoiType poiType, String cityCode,
                             QueryOptions options) {
        clear();
        long id = System.currentTimeMillis();
        checkQueryOptions(id, options);
        this.queryType = QueryType.CIRCLE;
        Circle circle = options.getCircle();
        queryOptions = null;
        queryOptions = new PoiSearch.Query(key, poiType.getStringValue());
        poiSearch = null;
        poiSearch = new PoiSearch(mapActivity != null ? mapActivity.getApplicationContext() : context, queryOptions);
        queryOptions.setPageNum(options.getiPageNumber());
        queryOptions.setPageSize(options.getiPageCount());
        PoiSearch.SearchBound searchBound = new PoiSearch.SearchBound(circle
                .getCenter().getAMapPt(), (int) circle.getRadius());
        poiSearch.setBound(searchBound);

        ExecutorService exec = Executors.newSingleThreadExecutor();
        exec.execute(new SearchThread(id, poiSearch, options));
        exec.shutdown();

        return id;
    }

    /**
     * Check query options
     *
     * @param options
     * @throws Exception if it is null.
     */
    private boolean checkQueryOptions(long id, QueryOptions options) {
        if (options == null) {
            Exception e = new Exception(
                    "Can't find query options from outside here.");
            sendErrorMessage(id, "Need query options", e,
                    ExceptionType.QUERY_OPTIONS_ERR);
            return false;
        }
        return true;
    }

    /**
     * @param poiSearch
     * @throws AMapException
     * @throws com.amap.api.services.core.AMapException
     * @throws Exception
     */
    private void search(long id, PoiSearch poiSearch, QueryOptions options)
            throws AMapException {

        mPoiPagedResult = poiSearch.searchPOI();
        this.totalPage = this.getPoiPageCount();
        if (totalPage > 0) {
            nextPageResult(id);
        } else {
            sendSearchResults(id, -1, -1);
        }
    }

    private void clear() {
        mSearchResultList.clear();
        currentPage = 0;
        mPoiPagedResult = null;
    }

    public long getNextPage() {
        long id = System.currentTimeMillis();
        ExecutorService exec = Executors.newSingleThreadExecutor();
        exec.execute(new NextPageThread(id));
        exec.shutdown();
        return id;
    }

    private void nextPageResult(long id) throws AMapException {
        this.queryOptions.setPageNum(currentPage);
        mPoiPagedResult = this.poiSearch.searchPOI();
        if (currentPage <= totalPage - 1) {
            sendSearchResults(id);

        } else {
            sendSearchResults(id, -1, -1);
        }
    }

    /**
     * @param id
     */
    private void sendSearchResults(long id) {
        mSearchResultList.clear();
        if (mPoiPagedResult.getPois() != null && mPoiPagedResult.getPois().size() > 0) {
            for (PoiItem poi : mPoiPagedResult.getPois()) {
                mSearchResultList.add(this.getSearchResult(poi));
            }
            sendSearchResults(id, currentPage, totalPage);
            currentPage++;
        }
    }

    /**
     * @param id the thread id
     */
    private void sendSearchResults(long id, int currentPage, int totalPage) {
        SearchResults results = new SearchResults(id, queryType, currentPage,
                totalPage, mSearchResultList);
        msgHandler.sendMessage(Message.obtain(msgHandler,
                Constants.SEARCH_RESULT, results));
    }

    public int getPoiPageCount() {
        if (mPoiPagedResult != null) {
            return mPoiPagedResult.getPageCount();
        } else {
            return 0;
        }
    }

    private PoiItemInfo getSearchResult(PoiItem poiItem) {

        if (poiItem == null) {
            return null;
        }
        PoiItemInfo poiItemInfo = new PoiItemInfo();
        poiItemInfo.setLatitude(poiItem.getLatLonPoint().getLatitude());
        poiItemInfo.setLongitude(poiItem.getLatLonPoint().getLongitude());
        poiItemInfo.setAddress(poiItem.getSnippet());
        poiItemInfo.setTelphone(poiItem.getTel());
        poiItemInfo.setTypeDes(poiItem.getTypeDes());

        poiItemInfo.setTitle(poiItem.getTitle());
        poiItemInfo.setCityCode(poiItem.getCityCode());
        poiItemInfo.setCityName(poiItem.getCityName());
        poiItemInfo.setWebSite(poiItem.getWebsite());
        poiItemInfo.setDiscountInfo(poiItem.isDiscountInfo());
        poiItemInfo.setGroupBuy(poiItem.isGroupbuyInfo());

        poiItemInfo.setDistance(poiItem.getDistance());

        return poiItemInfo;
    }

    public long geoCoderGps(double latitude, double longitude) {
        long id = System.currentTimeMillis();
        // ExecutorService exec = Executors.newSingleThreadExecutor();
        // exec.execute(new GeoCoderThread(latitude, longitude, id));
        // exec.shutdown();

        GeocodeSearch geocoder = new GeocodeSearch(mapActivity != null ? mapActivity.getApplicationContext() : context);
        GeoCoderThread listener = new GeoCoderThread(latitude, longitude, id);
        geocoder.setOnGeocodeSearchListener(listener);

        RegeocodeQuery regeoOpts = new RegeocodeQuery(new LatLonPoint(latitude,
                longitude), 500, GeocodeSearch.AMAP);
        geocoder.getFromLocationAsyn(regeoOpts);

//		Geocoder geocoder1 = new Geocoder(mapActivity!=null?mapActivity:context);
        return id;
    }

    private PoiItemInfo reverseGeoCoder(RegeocodeAddress addressList,
                                        double latitude, double longitude) {

        PoiItemInfo poiInfo = null;
        PoiItemInfo crossInfo = null;
        PoiItemInfo roadInfo = null;

        if (addressList == null) {
            return null;
        }

        List<PoiItem> pois = addressList.getPois();
        String province = addressList.getProvince();
        String cityName = addressList.getCity();
        if (cityName == null || cityName.isEmpty() || cityName.equalsIgnoreCase("{}")) {
            cityName = province;
        }
        if (pois != null && pois.size() > 0) {
            poiInfo = createPOIInfo(latitude, longitude, pois.get(0), addressList, cityName);
            return poiInfo;
        }
        String building = addressList.getBuilding();
        if (building != null && !building.isEmpty()) {
            poiInfo = createPOIInfo(latitude, longitude, building, cityName, province);
            return poiInfo;
        }
        List<Crossroad> cross = addressList.getCrossroads();
        if (crossInfo == null && cross != null && cross.size() > 0) {
            String title = cross.get(0).getFirstRoadName() + "/"
                    + cross.get(0).getSecondRoadName();
            crossInfo = createPOIInfo(latitude, longitude, title, cityName, province);
        }

        StreetNumber street = addressList.getStreetNumber();
        if (street != null && street.getStreet() != null
                && !street.getStreet().isEmpty()) {
            String title = street.getStreet() + street.getNumber();
            roadInfo = createPOIInfo(latitude, longitude, title, cityName, province);
            return roadInfo;
        }

        List<RegeocodeRoad> roads = addressList.getRoads();
        if (roads != null && roads.size() > 0) {
            String title = roads.get(0).getName();
            roadInfo = createPOIInfo(latitude, longitude, title, cityName, province);
            return roadInfo;
        }

        return null;
    }

    private PoiItemInfo createPOIInfo(double latitude, double longitude,
                                      String title, String cityName, String provinceInfo) {
        PoiItemInfo poiInfo = new PoiItemInfo();

        poiInfo.setCityName(cityName);
        poiInfo.setLatitude(latitude);
        poiInfo.setLongitude(longitude);
        poiInfo.setTitle(title);
        poiInfo.setAddress(title);
        poiInfo.setProvince(provinceInfo);
        return poiInfo;
    }

    /**
     * @param latitude
     * @param longitude
     * @param address
     * @return
     */
    private PoiItemInfo createPOIInfo(double latitude, double longitude,
                                      PoiItem address, RegeocodeAddress addressList, String cityName) {
        PoiItemInfo poiInfo = new PoiItemInfo();

        poiInfo.setCityName(cityName);
        poiInfo.setLatitude(latitude);
        poiInfo.setLongitude(longitude);
        poiInfo.setTitle(address.getTitle());
        poiInfo.setTelphone(address.getTel());
        if (addressList.getStreetNumber() != null) {
            poiInfo.setAddress(addressList.getStreetNumber().getStreet() + addressList.getStreetNumber().getNumber());
        }

        return poiInfo;
    }

    /**
     * @return the mSearchResultList
     */
    public List<PoiItemInfo> getmSearchResultList() {
        return mSearchResultList;
    }

    public void registerSearchListener(OSearchHandlerListener resultListener) {
        msgHandler.registerSearchListener(resultListener);
    }

    public void removeListener() {
        msgHandler.removeSearchListener();
    }

    /**
     * @param e
     */
    private void sendErrorMessage(long id, String errorMsg, Exception e,
                                  ExceptionType type) {
        MapException exp = new MapException(id, errorMsg, e, type);
        msgHandler.sendMessage(Message.obtain(msgHandler,
                Constants.SEARCH_ERRORS, exp));
    }

    class GeoCoderThread implements OnGeocodeSearchListener {

        double latitude;
        double longitude;
        long id;

        public GeoCoderThread(double latitude, double longitude, long id) {
            this.latitude = latitude;
            this.longitude = longitude;
            this.id = id;
        }

        @Override
        public void onGeocodeSearched(GeocodeResult arg0, int arg1) {
            // TODO Auto-generated method stub

        }

        @Override
        public void onRegeocodeSearched(RegeocodeResult res, int code) {
            if (code == 0) {
                try {
                    PoiItemInfo poi = reverseGeoCoder(
                            res.getRegeocodeAddress(), latitude, longitude);
                    if (poi == null) {
                        throw new Exception(
                                "Can't get result from reverse geocoder");
                    }
                    poi.setThreadId(id);
                    msgHandler.sendMessage(Message.obtain(msgHandler,
                            Constants.REVERSE_GEOCODE_RESULT, poi));
                } catch (Exception e) {
                    sendErrorMessage(id, e.getMessage(), e,
                            ExceptionType.REVERSE_GEO_CODE_ERR);
                }
            } else {
                String err = "reverse geocode error";
                sendErrorMessage(id, err, new Exception(err),
                        ExceptionType.REVERSE_GEO_CODE_ERR);
            }

        }

    }

    class SearchThread implements Runnable {

        PoiSearch poiSearch;
        QueryOptions options;
        long id;

        public SearchThread(long id, PoiSearch poiSearch, QueryOptions options) {
            this.poiSearch = poiSearch;
            this.options = options;
            this.id = id;
        }

        @Override
        public void run() {
            try {
                search(id, poiSearch, options);
            } catch (AMapException e) {
                sendErrorMessage(id, e.getErrorMessage(), e,
                        ExceptionType.POI_SEARCH_ERR);
            }
        }

    }

    class NextPageThread implements Runnable {

        long id;

        public NextPageThread(long id) {
            this.id = id;
        }

        @Override
        public void run() {
            try {
                nextPageResult(id);
            } catch (AMapException e) {
                sendErrorMessage(id, e.getErrorMessage(), e,
                        ExceptionType.NEXT_PAGE_ERR);
            }
        }

    }
}
